package com.liveinstars.account.application.endpoint;


import com.liveinstars.account.application.assembler.AccountAssembler;
import com.liveinstars.account.business.domain.bo.User;
import com.liveinstars.account.business.exception.AccountExceptionEnum;
import com.liveinstars.account.business.service.AccountService;
import com.liveinstars.account.business.service.UserService;
import com.liveinstars.account.business.util.CommonUtil;
import com.liveinstars.account.business.util.Md5Util;
import com.liveinstars.api.dto.*;
import com.liveinstars.api.facade.UserAccountApi;
import com.onepiece.cache.service.CacheService;
import com.onepiece.shipelves.common.Result;
import com.onepiece.shipelves.common.enums.SystemExceptionEnum;
import com.onepiece.shipelves.common.exception.QuHuiRuntimeException;
import com.onepiece.shipelves.common.utils.AESPKCS7Util;
import com.onepiece.shipelves.common.utils.JsonWebTokenUtil;
import com.onepiece.shipelves.common.utils.JwtAccount;
import com.onepiece.shipelves.common.utils.StringUtil;
import io.jsonwebtoken.*;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Date;
import java.util.UUID;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest;
import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;

import static com.liveinstars.account.business.util.AesUtil.ENCODE_RULES;

@RestController
public class UserAccountEndpoint implements UserAccountApi {
    private static final Logger logger = LoggerFactory.getLogger(UserAccountEndpoint.class);


    @Autowired
    @Qualifier("AccountService")
    private AccountService accountService;


    @Autowired
    @Qualifier("UserService")
    private UserService userService;

    @Autowired
    CacheService cache;


    @Override
    public Result<UserLoginInfo> login(@RequestBody LoginRequest request) {
        Result<UserLoginInfo> result = new Result<>();
        String appId = "";
        UserLoginInfo loginInfo = new UserLoginInfo();
        try {

            if (isMobileLoginPost(request)) {
                accountService.loginByMobile(request.getMobile(), request.getCode());
                appId = request.getMobile();
            }

            //判断是否是第三方登陆，去查询第三方表是否存在id，存在赋值
            if (isSocialLoginPost(request)) {
                appId = accountService.loginBySocial(request.getType(), request.getUnionid(), request.getOpenid());
                if(StringUtils.isEmpty(appId)){
                    throw new QuHuiRuntimeException(AccountExceptionEnum.ENTITY_NOT_FOUND);
                }
            }


            // 判断是否是登录请求
            if (isPasswordLoginPost(request)) {
                boolean valid = accountService.validAccount(request.getUsername(), request.getPassword());
                if (valid) {
                    appId = request.getUsername();

                } else {
                    throw new QuHuiRuntimeException(AccountExceptionEnum.VALID_ACCOUNT_ERROR);
                }
            }

            if (!StringUtils.isEmpty(appId)) {
                String roles = accountService.loadAccountRole(appId);
                User authUser = userService.getUserByAppId(appId);
                if (authUser == null) {
                    throw new QuHuiRuntimeException(AccountExceptionEnum.ENTITY_NOT_FOUND);
                }
                // 时间以秒计算, 设置redis为7天，设置jwt有效时间为一半，但jwt失效的时候，请求将会
                // 使用redis中的refreshtoken刷新jwt，然后返回，客户端收到该回复后修改本地jwt然后继续发起请求
                //这样，只要用户在7天内使用过就不会导致退出登录
                String jwt = generateJwtAndCache(authUser.getId(), roles);
                authUser.setPassword(null);
                authUser.setSalt(null);
                GetUserInfo info = AccountAssembler.toUserInfo(authUser);
                loginInfo.setUserInfo(info);
                loginInfo.setJwt(jwt);
                result.setData(loginInfo);

            } else {
                throw new QuHuiRuntimeException(AccountExceptionEnum.LOGIN_ACCOUNT_ERROR);
            }
        } catch (Exception e) {
            logger.error("login error", e);
            if (e instanceof QuHuiRuntimeException) {
                result = ((QuHuiRuntimeException) e).createResult();
            } else {
                result = SystemExceptionEnum.UN_KNOW_ERROR.createResult();
            }
        }
        return result;
    }

    private String generateJwtAndCache(Integer userId, String roles) {
        int refreshPeriodTime = 604800;
        String jwt = JsonWebTokenUtil.issueJWT(UUID.randomUUID().toString(), String.valueOf(userId),
                "token-server", Long.valueOf(refreshPeriodTime >> 1), roles, null, SignatureAlgorithm.HS512);
        // 将签发的JWT存储到Redis： {JWT-SESSION-{appID} , jwt}
        cache.set("JWT-SESSION-" + userId, jwt, refreshPeriodTime);
        return jwt;
    }

    //当用户第一次使用app时用第三方登录需要输入账号密码或者使用手机输入验证码，需要先使用unionid进行login，调login失败再调这个绑定接口
    //绑定接口需要完成账号注册和第三方绑定信息新增功能
    @Override
    public Result<UserLoginInfo> bind(@RequestBody BindRequest request) {
        Result<UserLoginInfo> result = new Result<>();
        boolean success = false;
        UserLoginInfo loginInfo = new UserLoginInfo();
        try {
            String appId = "";
            if (isBindSocialPost(request)) {
                if (isPasswordBindPost(request)) {


                    String userName = request.getUserName();
                    String password = request.getPassword();
                    appId = userName;
                    boolean valid = accountService.validAccount(userName, password);
                    if (valid) {
                        success = accountService.bindSocialAccount(userName, request.getType(), request.getUnionid(), request.getOpenid());
                    } else {
                        throw new QuHuiRuntimeException(AccountExceptionEnum.VALID_ACCOUNT_ERROR);
                    }
                }

                //判断是否为第三方绑定手机
                if (isMobileBindPost(request)) {
                    try {
                        String mobile = request.getMobile();
                        String code = request.getCode();
                        String key = String.format("sms_login_%s", mobile);
                        appId = mobile;

                        accountService.loginByMobile(mobile, code);
                        success = accountService.bindSocialAccount(mobile, request.getType(), request.getUnionid(), request.getOpenid());
                    } catch (QuHuiRuntimeException e) {
                        // response 告知无效请求
                        result = ((QuHuiRuntimeException) e).createResult();

                    }
                }


                if (success) {
                    String roles = accountService.loadAccountRole(appId);
                    User authUser = userService.getUserByAppId(appId);
                    if (authUser == null) {
                        throw new QuHuiRuntimeException(AccountExceptionEnum.ENTITY_NOT_FOUND);
                    }
                    String jwt = generateJwtAndCache(authUser.getId(), roles);
                    authUser.setPassword(null);
                    authUser.setSalt(null);
                    GetUserInfo info = AccountAssembler.toUserInfo(authUser);
                    loginInfo.setUserInfo(info);
                    loginInfo.setJwt(jwt);
                    result.setData(loginInfo);

                } else {
                    throw new QuHuiRuntimeException(AccountExceptionEnum.BIND_ACCOUNT_ERROR);
                }

            }else if(isMobileBindPost(request) && !StringUtils.isEmpty(request.getUserName())){

                try {
                    String mobile = request.getMobile();
                    String code = request.getCode();

                    boolean valid = accountService.validMobileCode(mobile, code);
                    if(valid){
                        success = accountService.bindMobile(request.getUserName(), mobile);
                    }
                } catch (QuHuiRuntimeException e) {
                    // response 告知无效请求
                    result = ((QuHuiRuntimeException) e).createResult();

                }
            }
        } catch (Exception e) {
            logger.error("bind error", e);
            if (e instanceof QuHuiRuntimeException) {
                result = ((QuHuiRuntimeException) e).createResult();
            } else {
                result = SystemExceptionEnum.UN_KNOW_ERROR.createResult();
            }

        }

        return result;

    }


    private boolean isMobileLoginPost(LoginRequest request) {

        return
                null != request.getMobile()
                        && null != request.getCode();
    }

    private boolean isPasswordLoginPost(LoginRequest request) {

        return
                null != request.getPassword()
                        && null != request.getUsername();
    }

    private boolean isSocialLoginPost(LoginRequest request) {
        return
                null != request.getType()
                        && null != request.getUnionid()
                        && null != request.getOpenid();
    }

    private boolean isBindSocialPost(BindRequest request) {
        return
                null != request.getType()
                        && null != request.getUnionid()
                        && null != request.getOpenid();
    }

    private boolean isPasswordBindPost(BindRequest request) {
        return
                null != request.getPassword()
                        && null != request.getUserName();
    }

    private boolean isMobileBindPost(BindRequest request) {
        return
                null != request.getMobile()
                        && null != request.getCode();
    }

    @Override
    public Result<Boolean> register(@RequestBody RegisterRequest request) {
        Result<Boolean> result = new Result<>();
        try {

            User authUser = new User();
            if (isAccountRegisterPost(request)) {

                String userName = request.getUserName();
                String password = request.getPassword();

                if (accountService.isAccountExistByUserName(userName)) {
                    // 账户已存在
                    throw new QuHuiRuntimeException(AccountExceptionEnum.ACCOUNT_EXIST);
                } else {
                    authUser.setUserName(userName);
                    authUser.setNickName(userName);

                    String realPassword = AESPKCS7Util.decrypt(password, ENCODE_RULES);
                    String salt = CommonUtil.getRandomString(6);
                    // 存储到数据库的密码为 MD5(原密码+盐值)
                    String md = Md5Util.md5(realPassword + salt);
                    logger.info("register realPassword={} {} {}", realPassword,salt,md);

                    authUser.setPassword(md);
                    authUser.setSalt(salt);
                    authUser.setCreateTime(new Date());
                    authUser.setStatus((byte) 1);
                    if (accountService.registerAccount(authUser)) {
                        result.setData(true);
                    } else {
                        result.setData(false);
                    }
                }
            } else {
                throw new QuHuiRuntimeException(AccountExceptionEnum.REQUEST_ERROR);
            }
        } catch (Exception e) {
            if (e instanceof QuHuiRuntimeException) {
                result = ((QuHuiRuntimeException) e).createResult();
            } else {
                result = SystemExceptionEnum.UN_KNOW_ERROR.createResult();
            }
        }
        return result;
    }

    @Override
    public Result<OssToken> ossToken() {


        String accessKeyId = "LTAI4GC5DGuRKGRMyJLRLhbR";
        String accessKeySecret = "td6SlD1Z4UtMu3clGJRjhwTF6oeHu5";

        // AssumeRole API 请求参数: RoleArn, RoleSessionName, Policy, and
        // DurationSeconds
        // RoleArn可以到控制台上获取，路径是 访问控制 > 角色管理 > 角色名称 > 基本信息 > Arn
        String roleArn = "acs:ram::1729592018948204:role/aliyunosstokengeneratorrole";

        // RoleSessionName 是临时Token的会话名称，自己指定用于标识你的用户，主要用于审计，或者用于区分Token颁发给谁
        // 但是注意RoleSessionName的长度和规则，不要有空格，只能有'-' '_' 字母和数字等字符
        // 具体规则请参考API文档中的格式要求
        String roleSessionName = "alice-001";

        // 如何定制你的policy，如果policy为null，则STS的权限与roleArn的policy的定义的权限
        String policy = "{\n" +
                "    \"Version\": \"1\", \n" +
                "    \"Statement\": [\n" +
                "        {\n" +
                "            \"Action\": [\n" +
                "                \"oss:GetBucket\", \n" +
                "                \"oss:PutObject\", \n" +
                "                \"oss:GetObject\", \n" +
                "                \"oss:ListParts\" \n" +
                "            ], \n" +
                "            \"Resource\": [\n" +
                "                \"acs:oss:*:*:*\"\n" +
                "            ], \n" +
                "            \"Effect\": \"Allow\"\n" +
                "        }\n" +
                "    ]\n" +
                "}";

        // 过期时间设置默认是一小时，单位秒有效值是[900, 3600]，即15分钟到60分钟。
        long expired = 3600;

        // 此处必须为 HTTPS
        ProtocolType protocolType = ProtocolType.HTTPS;

        Result<OssToken> result = new Result<>();
        try {
            final AssumeRoleResponse stsResponse = assumeRole(accessKeyId, accessKeySecret, roleArn, roleSessionName,
                    policy, protocolType, expired);


            Map<String, String> respMap = new LinkedHashMap<String, String>();
            respMap.put("StatusCode", "200");
            respMap.put("AccessKeyId", stsResponse.getCredentials().getAccessKeyId());
            respMap.put("AccessKeySecret", stsResponse.getCredentials().getAccessKeySecret());
            respMap.put("SecurityToken", stsResponse.getCredentials().getSecurityToken());
            respMap.put("Expiration", stsResponse.getCredentials().getExpiration());

            OssToken ossToken = new OssToken();
            ossToken.setStatusCode("200");
            ossToken.setAccessKeyId(stsResponse.getCredentials().getAccessKeyId());
            ossToken.setAccessKeySecret(stsResponse.getCredentials().getAccessKeySecret());
            ossToken.setSecurityToken(stsResponse.getCredentials().getSecurityToken());
            ossToken.setExpiration(stsResponse.getCredentials().getExpiration());
            result.setData(ossToken);

        } catch (ClientException e) {
            result.setCode("500");
            result.setMessage(e.getErrMsg());
        }
        return result;
    }

    @Override
    public Result<Boolean> smsLogin(@RequestBody PhoneSmsRequest request) {
        return userService.smsLogin(request);
    }

    @Override
    public Result<UserLoginInfo> refreshToken(@RequestBody RefreshJWT request) {
        Result<UserLoginInfo> result = new Result<>();
        UserLoginInfo loginInfo = new UserLoginInfo();
        String token = request.getJwt();
        Integer userId = request.getUserId();
        if (StringUtils.isEmpty(token) || userId == null) {
            throw new QuHuiRuntimeException(AccountExceptionEnum.REQUEST_ERROR);
        }
        User authUser = userService.getUserById(userId);
        if (authUser == null) {
            throw new QuHuiRuntimeException(AccountExceptionEnum.ENTITY_NOT_FOUND);
        }
        JwtAccount jwtAccount = null;
        try {
            //jwt还有效直接返回
            jwtAccount = JsonWebTokenUtil.parseJwt(token, JsonWebTokenUtil.SECRET_KEY);
            GetUserInfo info = AccountAssembler.toUserInfo(authUser);
            loginInfo.setUserInfo(info);
            loginInfo.setJwt(request.getJwt());
            result.setData(loginInfo);
            return result;
        } catch (SignatureException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
            // 令牌错误
            logger.error("errJwt" + token);
        } catch (ExpiredJwtException e) {
            // 令牌过期
            logger.error("expiredJwt" + token);
            String refreshJwt = cache.get("JWT-SESSION-" + userId);
            if (null != refreshJwt && refreshJwt.equals(token)) {
                //如果客户端同时发起多次刷新的请求，那么判断如果老的token还在oldsession这个里面
                //那么也返回新的给它,防止因为多次刷新请求失败导致客户端用户态清空
                cache.set("JWT-OLD_SESSION-" + userId, token, 60);
                // 重新申请新的JWT
                // 根据appId获取其对应所拥有的角色(这里设计为角色对应资源，没有权限对应资源)
                String roles = accountService.loadAccountRole(authUser.getUserName());
                String jwt = generateJwtAndCache(authUser.getId(), roles);
                GetUserInfo info = AccountAssembler.toUserInfo(authUser);
                loginInfo.setUserInfo(info);
                loginInfo.setJwt(jwt);
                result.setData(loginInfo);
                return result;
            }
            String oldJwt = cache.get("JWT-OLD_SESSION-" + userId);
            if (null != oldJwt && oldJwt.equals(token)) {
                // 重新申请新的JWT
                // 根据appId获取其对应所拥有的角色(这里设计为角色对应资源，没有权限对应资源)
                GetUserInfo info = AccountAssembler.toUserInfo(authUser);
                loginInfo.setUserInfo(info);
                loginInfo.setJwt(refreshJwt);
                result.setData(loginInfo);
                return result;
            }

        } catch (Exception e) {
            logger.error("errJwt" + token);
        }
        if (null == jwtAccount) {
            logger.error("errJwt" + token);
        }

        return result;
    }

    @Override
    public Result<UpdateUserpwdVO> unifyAppUpdatePwd(@RequestBody UnifyAppUpdatepwdReq request, @RequestHeader("uid") Integer userId) {
        Result<UpdateUserpwdVO> result = new Result<>();
        request.checkRequiredParam();
        request.setUid(userId);
        UpdateUserpwdVO updateUserpwdVO;
        if ("1".equals(request.getUpdateType())) {
            updateUserpwdVO = accountService.updateUserPasswordByUserName(request);
        } else {
            updateUserpwdVO = accountService.updateUserPasswordByMobile(request);
        }
        result.setData(updateUserpwdVO);
        return result;
    }

    @Override
    public Result<Boolean> unifyUpdateUserInfo(@RequestBody UnifyAppUpdateUserInfoReq request, @RequestHeader("uid") Integer userId) {
        Result<Boolean> result = new Result<>();
        try {
            if (!userId.equals(request.getUid())) {
                throw new QuHuiRuntimeException(SystemExceptionEnum.ILLEGAL_REQUEST_PARAM);
            }
            boolean success = userService.updateUserInfo(request);
            result.setData(success);
        } catch (Exception e) {
            if (e instanceof QuHuiRuntimeException) {
                result = ((QuHuiRuntimeException) e).createResult();
            } else {
                result = SystemExceptionEnum.UN_KNOW_ERROR.createResult();
            }
        }
        return result;
    }

    private static final long serialVersionUID = 5522372203700422672L;
    // 目前只有"cn-hangzhou"这个region可用, 不要使用填写其他region的值
    public static final String REGION_CN_HANGZHOU = "cn-hangzhou";
    public static final String STS_API_VERSION = "2015-04-01";

    protected AssumeRoleResponse assumeRole(String accessKeyId, String accessKeySecret, String roleArn,
                                            String roleSessionName, String policy, ProtocolType protocolType, long durationSeconds) throws ClientException {
        try {
            // 创建一个 Aliyun Acs Client, 用于发起 OpenAPI 请求
            IClientProfile profile = DefaultProfile.getProfile(REGION_CN_HANGZHOU, accessKeyId, accessKeySecret);
            DefaultAcsClient client = new DefaultAcsClient(profile);

            // 创建一个 AssumeRoleRequest 并设置请求参数
            final AssumeRoleRequest request = new AssumeRoleRequest();
            request.setVersion(STS_API_VERSION);
            request.setMethod(MethodType.POST);
            request.setProtocol(protocolType);

            request.setRoleArn(roleArn);
            request.setRoleSessionName(roleSessionName);
            request.setPolicy(policy);
            request.setDurationSeconds(durationSeconds);

            // 发起请求，并得到response
            final AssumeRoleResponse response = client.getAcsResponse(request);

            return response;
        } catch (ClientException e) {
            throw e;
        }
    }


    private boolean isAccountRegisterPost(RegisterRequest request) {

        return
                null != request.getUserName()
                        && null != request.getPassword();
    }

}
